// Copyright (C) 1999-2000 Id Software, Inc.
//

// cg_localents.c -- every frame, generate renderer commands for locally
// processed entities, like smoke puffs, gibs, shells, etc.

#include "cg_local.h"
#include "cgame_BOTS.h"

// BEGIN bots - tbp
//#define	MAX_LOCAL_ENTITIES	512
// More local entities! Yes!
#define	MAX_LOCAL_ENTITIES	1024
// END bots - tbp

localEntity_t	cg_localEntities[MAX_LOCAL_ENTITIES];
localEntity_t	cg_activeLocalEntities;		// double linked list
localEntity_t	*cg_freeLocalEntities;		// single linked list

/*
===================
CG_InitLocalEntities

This is called at startup and for tournement restarts
===================
*/
void	CG_InitLocalEntities( void ) {
	int		i;

	memset( cg_localEntities, 0, sizeof( cg_localEntities ) );
	cg_activeLocalEntities.next = &cg_activeLocalEntities;
	cg_activeLocalEntities.prev = &cg_activeLocalEntities;
	cg_freeLocalEntities = cg_localEntities;
	for ( i = 0 ; i < MAX_LOCAL_ENTITIES - 1 ; i++ ) {
		cg_localEntities[i].next = &cg_localEntities[i+1];
	}
}


/*
==================
CG_FreeLocalEntity
==================
*/
void CG_FreeLocalEntity( localEntity_t *le ) {
	if ( !le->prev ) {
		CG_Error( "CG_FreeLocalEntity: not active" );
	}

	// remove from the doubly linked active list
	le->prev->next = le->next;
	le->next->prev = le->prev;

	// the free list is only singly linked
	le->next = cg_freeLocalEntities;
	cg_freeLocalEntities = le;
}

/*
===================
CG_AllocLocalEntity

Will allways succeed, even if it requires freeing an old active entity
===================
*/
localEntity_t	*CG_AllocLocalEntity( void ) {
	localEntity_t	*le;

	if ( !cg_freeLocalEntities ) {
		// no free entities, so free the one at the end of the chain
		// remove the oldest active entity
		CG_FreeLocalEntity( cg_activeLocalEntities.prev );
	}

	le = cg_freeLocalEntities;
	cg_freeLocalEntities = cg_freeLocalEntities->next;

	memset( le, 0, sizeof( *le ) );

	// link into the active list
	le->next = cg_activeLocalEntities.next;
	le->prev = &cg_activeLocalEntities;
	cg_activeLocalEntities.next->prev = le;
	cg_activeLocalEntities.next = le;
	return le;
}


/*
====================================================================================

FRAGMENT PROCESSING

A fragment localentity interacts with the environment in some way (hitting walls),
or generates more localentities along a trail.

====================================================================================
*/

/*
================
CG_BloodTrail

Leave expanding blood puffs behind gibs
================
*/
void CG_BloodTrail( localEntity_t *le ) {
	int		t;
	int		t2;
	int		step;
	vec3_t	newOrigin;
	localEntity_t	*blood;

	step = 150;
	t = step * ( (cg.time - cg.frametime + step ) / step );
	t2 = step * ( cg.time / step );

	for ( ; t <= t2; t += step ) {
		BG_EvaluateTrajectory( &le->pos, t, newOrigin );

		blood = CG_SmokePuff( newOrigin, vec3_origin, 
					  20,		// radius
					  1, 1, 1, 1,	// color
					  2000,		// trailTime
					  t,		// startTime
					  0,		// flags
					  cgs.media.bloodTrailShader );
		// use the optimized version
		blood->leType = LE_FALL_SCALE_FADE;
		// drop a total of 40 units over its lifetime
		blood->pos.trDelta[2] = 40;
	}
}


/*
================
CG_FragmentBounceMark
================
*/
void CG_FragmentBounceMark( localEntity_t *le, trace_t *trace ) {
	int			radius;

	if ( le->leMarkType == LEMT_BLOOD ) {

		radius = 16 + (rand()&31);
		CG_ImpactMark( cgs.media.bloodMarkShader, trace->endpos, trace->plane.normal, random()*360,
			1,1,1,1, qtrue, radius, qfalse );
	}

	// don't allow a fragment to make multiple marks, or they
	// pile up while settling
	le->leMarkType = LEMT_NONE;
}

/*
================
CG_FragmentBounceSound
================
*/
void CG_FragmentBounceSound( localEntity_t *le, trace_t *trace ) {
	if ( le->leBounceSoundType == LEBS_BLOOD ) {
		// half the gibs will make splat sounds
		if ( rand() & 1 ) {
			int r = rand()&3;
			sfxHandle_t	s;

			if ( r < 2 ) {
				s = cgs.media.gibBounce1Sound;
			} else if ( r == 2 ) {
				s = cgs.media.gibBounce2Sound;
			} else {
				s = cgs.media.gibBounce3Sound;
			}
			trap_S_StartSound( trace->endpos, ENTITYNUM_WORLD, CHAN_AUTO, s );
		}
	} else if ( le->leBounceSoundType == LEBS_BRASS ) {

	}

	// don't allow a fragment to make multiple bounce sounds,
	// or it gets too noisy as they settle
	le->leBounceSoundType = LEBS_NONE;
}


/*
================
CG_ReflectVelocity
================
*/
void CG_ReflectVelocity( localEntity_t *le, trace_t *trace ) {
	vec3_t	velocity;
	float	dot;
	int		hitTime;

	// reflect the velocity on the trace plane
	hitTime = cg.time - cg.frametime + cg.frametime * trace->fraction;
	BG_EvaluateTrajectoryDelta( &le->pos, hitTime, velocity );
	dot = DotProduct( velocity, trace->plane.normal );
	VectorMA( velocity, -2*dot, trace->plane.normal, le->pos.trDelta );

	VectorScale( le->pos.trDelta, le->bounceFactor, le->pos.trDelta );

	VectorCopy( trace->endpos, le->pos.trBase );
	le->pos.trTime = cg.time;


	// check for stop, making sure that even on low FPS systems it doesn't bobble
	if ( trace->allsolid || 
		( trace->plane.normal[2] > 0 && 
		( le->pos.trDelta[2] < 40 || le->pos.trDelta[2] < -cg.frametime * le->pos.trDelta[2] ) ) ) {
		le->pos.trType = TR_STATIONARY;
	} else {

	}
}

/*
================
CG_AddFragment
================
*/
void CG_AddFragment( localEntity_t *le ) {
	vec3_t	newOrigin;
	trace_t	trace;

	if ( le->pos.trType == TR_STATIONARY ) {
		// sink into the ground if near the removal time
		int		t;
		float	oldZ;
		
		t = le->endTime - cg.time;
		if ( t < SINK_TIME ) {
			// we must use an explicit lighting origin, otherwise the
			// lighting would be lost as soon as the origin went
			// into the ground
			VectorCopy( le->refEntity.origin, le->refEntity.lightingOrigin );
			le->refEntity.renderfx |= RF_LIGHTING_ORIGIN;
			oldZ = le->refEntity.origin[2];
			le->refEntity.origin[2] -= 16 * ( 1.0 - (float)t / SINK_TIME );
			trap_R_AddRefEntityToScene( &le->refEntity );
			le->refEntity.origin[2] = oldZ;
		} else {
			trap_R_AddRefEntityToScene( &le->refEntity );
		}

		return;
	}

	// calculate new position
	BG_EvaluateTrajectory( &le->pos, cg.time, newOrigin );

	// trace a line from previous position to new position
	CG_Trace( &trace, le->refEntity.origin, NULL, NULL, newOrigin, -1, CONTENTS_SOLID );
	if ( trace.fraction == 1.0 ) {
		// still in free fall
		VectorCopy( newOrigin, le->refEntity.origin );

		if ( le->leFlags & LEF_TUMBLE ) {
			vec3_t angles;

			BG_EvaluateTrajectory( &le->angles, cg.time, angles );
			AnglesToAxis( angles, le->refEntity.axis );
		}

		trap_R_AddRefEntityToScene( &le->refEntity );

		// add a blood trail
		if ( le->leBounceSoundType == LEBS_BLOOD ) {
			CG_BloodTrail( le );
		}

		return;
	}

	// if it is in a nodrop zone, remove it
	// this keeps gibs from waiting at the bottom of pits of death
	// and floating levels
	if ( trap_CM_PointContents( trace.endpos, 0 ) & CONTENTS_NODROP ) {
		CG_FreeLocalEntity( le );
		return;
	}

	// leave a mark
	CG_FragmentBounceMark( le, &trace );

	// do a bouncy sound
	CG_FragmentBounceSound( le, &trace );

	// reflect the velocity on the trace plane
	CG_ReflectVelocity( le, &trace );

	trap_R_AddRefEntityToScene( &le->refEntity );
}

/*
=====================================================================

TRIVIAL LOCAL ENTITIES

These only do simple scaling or modulation before passing to the renderer
=====================================================================
*/

/*
====================
CG_AddFadeRGB
====================
*/
void CG_AddFadeRGB( localEntity_t *le ) {
	refEntity_t *re;
	float c;

	re = &le->refEntity;

	c = ( le->endTime - cg.time ) * le->lifeRate;
	c *= 0xff;

	re->shaderRGBA[0] = le->color[0] * c;
	re->shaderRGBA[1] = le->color[1] * c;
	re->shaderRGBA[2] = le->color[2] * c;
	re->shaderRGBA[3] = le->color[3] * c;

	trap_R_AddRefEntityToScene( re );
}

/*
==================
CG_AddMoveScaleFade
==================
*/
static void CG_AddMoveScaleFade( localEntity_t *le ) {
	refEntity_t	*re;
	float		c;
	vec3_t		delta;
	float		len;

	re = &le->refEntity;

	// fade / grow time
	c = ( le->endTime - cg.time ) * le->lifeRate;

	re->shaderRGBA[3] = 0xff * c * le->color[3];

	if ( !( le->leFlags & LEF_PUFF_DONT_SCALE ) ) {
		re->radius = le->radius * ( 1.0 - c ) + 8;
	}

	BG_EvaluateTrajectory( &le->pos, cg.time, re->origin );

	// if the view would be "inside" the sprite, kill the sprite
	// so it doesn't add too much overdraw
	VectorSubtract( re->origin, cg.refdef.vieworg, delta );
	len = VectorLength( delta );
	if ( len < le->radius ) {
		CG_FreeLocalEntity( le );
		return;
	}

	trap_R_AddRefEntityToScene( re );
}


/*
===================
CG_AddScaleFade

For rocket smokes that hang in place, fade out, and are
removed if the view passes through them.
There are often many of these, so it needs to be simple.
===================
*/
static void CG_AddScaleFade( localEntity_t *le ) {
	refEntity_t	*re;
	float		c;
	vec3_t		delta;
	float		len;

	re = &le->refEntity;

	// fade / grow time
	c = ( le->endTime - cg.time ) * le->lifeRate;

	re->shaderRGBA[3] = 0xff * c * le->color[3];
	re->radius = le->radius * ( 1.0 - c ) + 8;

	// if the view would be "inside" the sprite, kill the sprite
	// so it doesn't add too much overdraw
	VectorSubtract( re->origin, cg.refdef.vieworg, delta );
	len = VectorLength( delta );
	if ( len < le->radius ) {
		CG_FreeLocalEntity( le );
		return;
	}

	trap_R_AddRefEntityToScene( re );
}


/*
=================
CG_AddFallScaleFade

This is just an optimized CG_AddMoveScaleFade
For blood mists that drift down, fade out, and are
removed if the view passes through them.
There are often 100+ of these, so it needs to be simple.
=================
*/
static void CG_AddFallScaleFade( localEntity_t *le ) {
	refEntity_t	*re;
	float		c;
	vec3_t		delta;
	float		len;

	re = &le->refEntity;

	// fade time
	c = ( le->endTime - cg.time ) * le->lifeRate;

	re->shaderRGBA[3] = 0xff * c * le->color[3];

	re->origin[2] = le->pos.trBase[2] - ( 1.0 - c ) * le->pos.trDelta[2];

	re->radius = le->radius * ( 1.0 - c ) + 16;

	// if the view would be "inside" the sprite, kill the sprite
	// so it doesn't add too much overdraw
	VectorSubtract( re->origin, cg.refdef.vieworg, delta );
	len = VectorLength( delta );
	if ( len < le->radius ) {
		CG_FreeLocalEntity( le );
		return;
	}

	trap_R_AddRefEntityToScene( re );
}



/*
================
CG_AddExplosion
================
*/
static void CG_AddExplosion( localEntity_t *ex ) {
	refEntity_t	*ent;

	ent = &ex->refEntity;

	// add the entity
	trap_R_AddRefEntityToScene(ent);

	// add the dlight
	if ( ex->light ) {
		float		light;

		light = (float)( cg.time - ex->startTime ) / ( ex->endTime - ex->startTime );
		if ( light < 0.5 ) {
			light = 1.0;
		} else {
			light = 1.0 - ( light - 0.5 ) * 2;
		}
		light = ex->light * light;
		trap_R_AddLightToScene(ent->origin, light, ex->lightColor[0], ex->lightColor[1], ex->lightColor[2] );
	}
}

/*
================
CG_AddSpriteExplosion
================
*/
static void CG_AddSpriteExplosion( localEntity_t *le ) {
	refEntity_t	re;
	float c;

	re = le->refEntity;

	c = ( le->endTime - cg.time ) / ( float ) ( le->endTime - le->startTime );
	if ( c > 1 ) {
		c = 1.0;	// can happen during connection problems
	}

	re.shaderRGBA[0] = 0xff;
	re.shaderRGBA[1] = 0xff;
	re.shaderRGBA[2] = 0xff;
	re.shaderRGBA[3] = 0xff * c * 0.33;

	re.reType = RT_SPRITE;
	re.radius = 42 * ( 1.0 - c ) + 30;

	trap_R_AddRefEntityToScene( &re );

	// add the dlight
	if ( le->light ) {
		float		light;

		light = (float)( cg.time - le->startTime ) / ( le->endTime - le->startTime );
		if ( light < 0.5 ) {
			light = 1.0;
		} else {
			light = 1.0 - ( light - 0.5 ) * 2;
		}
		light = le->light * light;
		trap_R_AddLightToScene(re.origin, light, le->lightColor[0], le->lightColor[1], le->lightColor[2] );
	}
}

//==============================================================================

// BEGIN bots - tbp

void BOTS_Ray(vec3_t v1, vec3_t v2, localEntity_t *le, int lvl) {
	if (--lvl > 0) {
		// Split some more
		vec3_t p;
		int i;
		const float 
			fJITTER = 15.0f,
			fCUT	= 0.2f + (random()*0.6f);

		for (i=0; i<3; i++) {
			p[i] = v1[i] + (v2[i] - v1[i])*fCUT + crandom() * fJITTER;
		}

		/*
		if (le->leFlags & LEF_DISTANCE_ATTENUATE) {
			ProjectPointOnPlane();
			BOTS_Ray(v1,p,le,lvl);
			BOTS_Ray(p,v2,le,lvl);
		}
		*/
		BOTS_Ray(v1,p,le,lvl);
		BOTS_Ray(p,v2,le,lvl);
	}
	else {
		refEntity_t *re = &le->refEntity;


		// Render it
		VectorCopy( v1, re->origin );
		VectorCopy( v2, re->oldorigin );
		// re->frame = le->radius;
		re->renderfx = RF_NOSHADOW;
		trap_R_AddRefEntityToScene( re );
	}
}





void CG_AddRay( localEntity_t *le ) {
	refEntity_t *re,ent;
	float		c;


	re = &le->refEntity;


	c = ( le->endTime - cg.time ) * le->lifeRate;
	c *= 0xff;

#if 0
	re->shaderRGBA[0] = le->color[0] * c;
	re->shaderRGBA[1] = le->color[1] * c;
	re->shaderRGBA[2] = le->color[2] * c;
	re->shaderRGBA[3] = le->color[3] * c;
	BOTS_Ray(le->pos.trBase,le->pos.trDelta,le,4);
#endif


	memset (&ent, 0, sizeof(ent));
	AxisClear( ent.axis );

	ent.reType = le->leMarkType;
	ent.renderfx = RF_NOSHADOW;
	ent.frame = cg_extravaganza.integer;

	ent.customShader = 
		//rand()&1?
		trap_R_RegisterShader("bots/Ray");
		//trap_R_RegisterShader("textures/sfx/hologirl.jpg");

	VectorCopy(le->pos.trBase,		ent.origin);
	VectorCopy(le->pos.trDelta,		ent.oldorigin);

	ent.shaderRGBA[0] = ent.shaderRGBA[0] = ent.shaderRGBA[0] = ent.shaderRGBA[0] = 0xff;
	trap_R_AddRefEntityToScene(&ent);
}

void BOTS_TendrilSplit(vec3_t v1, vec3_t v2, refEntity_t *re, int lvl) {
	if (--lvl > 0) {
		// Split some more
		vec3_t p;
		int i;
		const float 
			fJITTER = 15.0f,
			fCUT	= 0.2f + (random()*0.6f);

		for (i=0; i<3; i++) {
			p[i] = v1[i] + (v2[i] - v1[i])*fCUT + crandom() * fJITTER;
		}

		
		BOTS_TendrilSplit(v1,p,re,lvl);
		BOTS_TendrilSplit(p,v2,re,lvl);
	}
	else {
		// Render it
		VectorCopy( v1, re->origin );
		VectorCopy( v2, re->oldorigin );
		trap_R_AddRefEntityToScene( re );
	}
}





void CG_AddTendril( localEntity_t *le ) {
	//CG_AddFadeRGB
	refEntity_t *re;
	float		c;
	centity_t	*bolt = cg_entities + le->leMarkType;
	int			clientNum = le->leBounceSoundType, i;
	vec3_t		start,end;



	// CG_Printf(va("EV_TENDRIL: %i\n",clientNum));

	re = &le->refEntity;

	c = ( le->endTime - cg.time ) * le->lifeRate;
	c *= 0xff;

	re->shaderRGBA[0] = le->color[0] * c;
	re->shaderRGBA[1] = le->color[1] * c;
	re->shaderRGBA[2] = le->color[2] * c;
	re->shaderRGBA[3] = le->color[3] * c;



	VectorCopy(bolt->lerpOrigin, start);
	if (clientNum == cg.snap->ps.clientNum) {
		VectorCopy(cg.predictedPlayerEntity.lerpOrigin, end);
	}
	else
		VectorCopy(cg_entities[clientNum].lerpOrigin, end);

	// Randomize the end point a bit
	end[2] += 12.0f;
	for (i=0; i<3; i++)
		end[i] += crandom() * (56.0f/3.0f);
	
	BOTS_TendrilSplit(start,end,re,4);
}

// Prototype.
// void CG_PlayerAngles( centity_t *cent, vec3_t legs[3], vec3_t torso[3], vec3_t head[3] );
void CG_AddWarcry( localEntity_t *le ) {
	int			clientNum = le->leMarkType;
	refEntity_t	*re;
	centity_t	*captain;
	vec3_t		headAngles;
	float		t;
	const float 
			limitU = 0.1f,
			limitD = 1.0f - limitU;


	t = 1.0f - (( le->endTime - cg.time ) / ( float ) ( le->endTime - le->startTime ));
	if ( t>1 ) {
		t = 1.0f;	// can happen during connection problems
	}
	if ( t<0 ) {
		t = 0.0f;
	}

	re = &le->refEntity;

	// Fade out @ the end
	if (t>limitU) {
		float c = ((limitD - (t - limitU))/limitD) * 0xff;

		re->shaderRGBA[0] = le->color[0] * 0xff;
		re->shaderRGBA[1] = le->color[1] * 0xff;
		re->shaderRGBA[2] = le->color[2] * 0xff;
		re->shaderRGBA[3] = c; // le->color[3] * c;
	}
	else {
		re->shaderRGBA[0] = le->color[0] * 0xff;
		re->shaderRGBA[1] = le->color[1] * 0xff;
		re->shaderRGBA[2] = le->color[2] * 0xff;
		re->shaderRGBA[3] = 0xff;
	}

	// If we're doing it for this player, use the predicted position
	if (clientNum == cg.snap->ps.clientNum) {
		captain = &cg.predictedPlayerEntity;
	}
	else
		captain = &cg_entities[clientNum];
	
	
	// Rotation
	// CG_PlayerAngles( cent, legs.axis, torso.axis, head.axis );
	VectorCopy( captain->lerpAngles, headAngles );
	// headAngles[YAW] = AngleMod( headAngles[YAW] );
	headAngles[YAW] -= 90.0f;
	AnglesToAxis( headAngles, re->axis );

	// Position
	VectorCopy(captain->lerpOrigin, re->origin);
	re->origin[2] += 32;
	VectorCopy( re->origin, re->lightingOrigin );

	// Scale
	t *= t;
	t = 1.0f + (t*15.0f);
	VectorScale( re->axis[0], t, re->axis[0] );
	VectorScale( re->axis[1], t, re->axis[1] );
	VectorScale( re->axis[2], t, re->axis[2] );
	re->nonNormalizedAxes = qtrue;

	// Render
	trap_R_AddRefEntityToScene(re);
}




// Add some sprites turning around a player... try to follow him
static void CG_AddHealRadius( localEntity_t *le ) {
	float		
				t		= 1.0f - (( le->endTime - cg.time ) / ( float ) ( le->endTime - le->startTime )),
				color;
	refEntity_t	*ent	= &le->refEntity;
	vec3_t		p;
	int	
		i,
		clientNum = le->leFlags;
	const int	iMAX_FLOATING_STUFF = 4;
	const float	
		fDISTANCE	= 40.0f,
		fBOBBING	= 5.0f,
		fSTEP		= (2.0f*M_PI)/iMAX_FLOATING_STUFF,
		fINITIAL_ROTATION = ent->rotation,
		fMAX_ROTATION = 4.0f * 2.0f * M_PI;



	if ( t > 1 )
		t = 1.0f;
	else if (t < 0)
		t = 0.0f;

	// If there's an alpha channel, use it to fade in/out
	// Ramp up at the beginning, then slowly ramp down
	if (t>0.2f)
		color = (t-0.2f)/0.8f;
	else
		color = 1.0f - (t/0.2f);
	ent->shaderRGBA[3] = 0xff * (1.0f - color); 


	// If we're doing it for this player, use the predicted position
	if (clientNum == cg.snap->ps.clientNum) {
		VectorCopy(cg.predictedPlayerEntity.lerpOrigin, p);
	}
	else
		VectorCopy(cg_entities[clientNum].lerpOrigin, p);
	p[2] += 56.0f/2.0f;

	ent->radius			= 10;
	ent->rotation		= 0;

	for (i=0; i<iMAX_FLOATING_STUFF; i++) {
		const float 
			a1 = ((float)i * fSTEP),
			a2 = a1 + (fMAX_ROTATION * t) + fINITIAL_ROTATION;


		VectorCopy(p,ent->origin);
		ent->origin[0] += sin(a2) * fDISTANCE;
		ent->origin[1] += cos(a2) * fDISTANCE;
		ent->origin[2] += sin(a1 + (t*2.0f*M_PI)) * fBOBBING;
		VectorCopy(ent->origin,ent->oldorigin);

		trap_R_AddRefEntityToScene( ent );
	}
	ent->rotation = fINITIAL_ROTATION; // restore
}

// Ball of fire, rendering phase
static void CG_AddBallOfFire( localEntity_t *le ) {
	float	tesselate   = (2.0f*M_PI)/14; // 16 > 500 polys
	float				c;

	
	c = ( le->endTime - cg.time ) / ( float ) ( le->endTime - le->startTime );
	if ( c > 1 ) {
		c = 1.0;	// can happen during connection problems
	}
	
	FX_TesselateSphere(
		le->refEntity.origin, 
		le->refEntity.customShader, // cgs.media.bloodMarkShader,
		le->refEntity.rotation, 
		tesselate, tesselate, 
		80.f + sin((M_PI/2.0f) * (1.0f - c)) * le->radius);
}

// Mushroom rendering phase...
// There's no need of that special processing now (beside the custom light fading). Hmm.
static void CG_AddMushroom( localEntity_t *le ) {
	refEntity_t	re;
	float		c;
	
	
	re = le->refEntity;
	
	c = ( le->endTime - cg.time ) / ( float ) ( le->endTime - le->startTime );
	if ( c > 1 ) {
		c = 1.0;	// can happen during connection problems
	}

	
	re.shaderRGBA[0] = 0xff;
	re.shaderRGBA[1] = 0xff;
	re.shaderRGBA[2] = 0xff;
	re.shaderRGBA[3] = 0xff * c * 0.33;
	
	trap_R_AddRefEntityToScene( &re );
	
	// add the light	
	if ( le->light ) {
		float		light;
		
		light = (float)( cg.time - le->startTime ) / ( le->endTime - le->startTime );
		if ( light < 0.2 ) {
			light = 1.0;
		} else {
			light = 1.0 - sin(light*(M_PI/2.0f)); // ( light - 0.5 ) * 2;
		}
		light = le->light * light;
		trap_R_AddLightToScene(re.origin, light, le->lightColor[0], le->lightColor[1], le->lightColor[2] );
	}

}

// Mushroom rendering phase... (old and sprite based)
#if 0
static void CG_AddMushroomOLD( localEntity_t *le ) {
	const float genTimeStep = 0.25f,
				genTimeBias = 0.45f;
	refEntity_t	re;
	float		c,t;
	
	re = le->refEntity;
	
	c = ( le->endTime - cg.time ) / ( float ) ( le->endTime - le->startTime );
	if ( c > 1 ) {
		c = 1.0;	// can happen during connection problems
	}
	
#if 1
	re.shaderRGBA[0] = 0xff;
	re.shaderRGBA[1] = 0xff;
	re.shaderRGBA[2] = 0xff;
	re.shaderRGBA[3] = 0xff * c * 0.33;
	
	re.reType = RT_SPRITE;
	
	if (le->leFlags	== 0)
		// Explosion core
		re.radius = 170 * sin( (1.0 - c)*M_PI ) + 20;
	else 
		// Main blast
		re.radius = 600 * ( 1.0 - sin(c*(M_PI/2.0f)))  + 80;
	
	// re.origin[2] = re.oldorigin[2] + (re.radius/2); // Compensate the radius being increased
	
	t = genTimeStep*(float)le->leFlags;
#if 0
	if ((le->leFlags == 1) && (c < (genTimeBias + t))) { // Core explosion spawning
		localEntity_t	*son;
		vec3_t			p;
		
		p[0] = re.origin[0]; //+ crandom() * 35.0f;
		p[1] = re.origin[1]; //+ crandom() * 35.0f;
		p[2] = re.oldorigin[2] + 30.0; //+ crandom() * 20.0f;
		
		CG_Printf(va("son at %f\n",c));
		son = CG_MakeMushroom( 
			p,
			cgs.media.dishFlashModel /*mod*/, cgs.media.rocketExplosionShader /*shader*/,
			t * 1000.0f  /*duration*/,
			le->leFlags - 1
			);
		le->leFlags		= -1;	// No exponential digression please ;)		
		son->light		= 0;	// 1 light is more than enough
	}
#endif
	trap_R_AddRefEntityToScene( &re );
	
	// add the light	
	if ( le->light ) {
		float		light;
		
		light = (float)( cg.time - le->startTime ) / ( le->endTime - le->startTime );
		if ( light < 0.2 ) {
			light = 1.0;
		} else {
			light = 1.0 - sin(light*(M_PI/2.0f)); // ( light - 0.5 ) * 2;
		}
		light = le->light * light;
		trap_R_AddLightToScene(re.origin, light, le->lightColor[0], le->lightColor[1], le->lightColor[2] );
	}
#endif	
}

#endif

void FX_Particle_Fountain(vec3_t origin, int iNb, const float fVV, const float fVH, const int iLifeTime) {
	float			
		angle;		
	const float		
		step			= (2.0*M_PI)/(float)iNb,
		fANGULAR_JITTER	= (M_PI/16.0f);	// Each gib is launched with a bit angular velocity randomness
	
	localEntity_t	*le;
	refEntity_t		*re;	


	// Pseudo particules generation.
	angle	= crandom() * M_PI; // Starts that layer somewhere
	while (iNb--) {
		float jitter = crandom() * fANGULAR_JITTER;
		
		le = CG_AllocLocalEntity();
		re = &le->refEntity;
		
		le->leType				= LE_PARTICLE;
		le->leFlags				= LEF_NO_STATIONARY|LEF_RANDOM_BOUNCE_SOUND|LEF_FADE_RGB;
		// le->leBounceSoundType	= trap_S_RegisterSound("bots/sfx/ricochet.wav");
		
		le->startTime	= cg.time;
		le->endTime		= le->startTime + iLifeTime + (crandom()*iLifeTime*0.5f); 
		le->lifeRate 	= 1.0f/(le->endTime - le->startTime); 
		
		le->pos.trType	= TR_GRAVITY; // TR_LINEAR; 
		le->pos.trTime	= cg.time - ( (rand()&127) + 20); // Add some jitter to its spawning
		le->pos.trDelta[0] = sin(angle+jitter) * fVH;
		le->pos.trDelta[1] = cos(angle+jitter) * fVH;
		le->pos.trDelta[2] = fVV;
		angle += step;
		
		VectorCopy( origin, re->origin );
		VectorCopy( origin, le->pos.trBase );
		
		// re->hModel			= cgs.media.ringFlashModel; // cgs.media.lightningExplosionModel;
		re->customShader	=	//cgs.media.bloodExplosionShader; // cgs.media.lightningShader;
								// cgs.media.golddust_blue_Shader;
								trap_R_RegisterShader( "bots/particle00" );
		
		le->bounceFactor	= 0.6;
		
		re->reType		= RT_SPRITE;
		re->radius		= 0.3f+random();
		re->rotation	= rand()%360;

		le->color[0] = le->color[1] = le->color[2] = le->color[3] = 1.0;
	}
}


void CG_AddParticle( localEntity_t *le ) {
	vec3_t	newOrigin;
	trace_t	trace;
	refEntity_t		*re = &le->refEntity;


	if (( le->leFlags & LEF_NO_STATIONARY ) && (le->pos.trType == TR_STATIONARY)) {
		CG_FreeLocalEntity( le );	// Remove if asked to
		return;
	}

	if (le->leFlags & LEF_FADE_RGB) {
		float c = ( le->endTime - cg.time ) * le->lifeRate * 0xff;
	
		re->shaderRGBA[0] = le->color[0] * c;
		re->shaderRGBA[1] = le->color[1] * c;
		re->shaderRGBA[2] = le->color[2] * c;
		re->shaderRGBA[3] = le->color[3] * c;
	}
	else {
		// Alpha
		if (le->leFlags & LEF_FADE_ALPHA) {
			float c = ( le->endTime - cg.time ) * le->lifeRate * 0xff;
			
			re->shaderRGBA[0] = le->color[0] * 0xff;
			re->shaderRGBA[1] = le->color[1] * 0xff;
			re->shaderRGBA[2] = le->color[2] * 0xff;

			re->shaderRGBA[3] = le->color[3] * c;
		}
		else {
			re->shaderRGBA[0] = le->color[0] * 0xff;
			re->shaderRGBA[1] = le->color[1] * 0xff;
			re->shaderRGBA[2] = le->color[2] * 0xff;
			re->shaderRGBA[3] = le->color[3] * 0xff;
		}
	}

	if (le->leFlags & LEF_SCINTILLATION) {
		float t = ( le->endTime - cg.time ) * le->lifeRate;

		//re->radius = sin( t * M_PI * 2.0f ) * ((random()*0.2f)+0.8f) * le->radius;
		re->radius = t * t * (1.0f - (random()*0.4f)) * le->radius;
		if (re->radius < 0.1) {
			CG_FreeLocalEntity( le );	// Too small, useless
			return;
		}
	}
	else
		re->radius = le->radius;

	// calculate new position
	BG_EvaluateTrajectory( &le->pos, cg.time, newOrigin );
	// trace a line from previous position to new position
	CG_Trace( &trace, le->refEntity.origin, NULL, NULL, newOrigin, -1, CONTENTS_SOLID );
	if ( trace.fraction == 1.0 ) {
		// still in free fall
		VectorCopy( newOrigin, le->refEntity.origin );
		trap_R_AddRefEntityToScene( &le->refEntity );
		return;
	}

	// if it is in a nodrop zone, remove it
	if ((le->leFlags &  LEF_NO_COLLISION) || (trap_CM_PointContents( trace.endpos, 0 ) & CONTENTS_NODROP)) {
		CG_FreeLocalEntity( le );
		return;
	}

	if (le->leBounceSoundType) {
		if (!( (le->leFlags & LEF_RANDOM_BOUNCE_SOUND) && (rand()&31) ))
			trap_S_StartSound( trace.endpos, ENTITYNUM_WORLD, CHAN_AUTO, le->leBounceSoundType );
		le->leBounceSoundType = 0; // Play *once* ;)
	}

	// reflect the velocity on the trace plane
	CG_ReflectVelocity( le, &trace );

	trap_R_AddRefEntityToScene( &le->refEntity );
}

static void CG_AddEffect1( localEntity_t *le ) {
	//refEntity_t	
		//*re = &le->refEntity;
	float		
		t = (float)( cg.time - le->startTime ) / ( le->endTime - le->startTime );
	int
		phase = le->leBounceSoundType;
	const float
		fTIME_OFFSET	= 0.0,
		fTIME_PHASE		= 0.05;



	switch (phase) {
	case 0:
		if (t>((fTIME_PHASE)+fTIME_OFFSET)) {
			phase++;
			
			// FX_Particle_Fountain(re->origin,(rand()&3)+2,300+(rand()&127),300+(rand()&255),600);
		}
		break;
	case 1:
		if (t>((2*fTIME_PHASE)+fTIME_OFFSET)) {
			phase++;
			// FX_Particle_Fountain(re->origin,(rand()&3)+2,300+(rand()&127),300+(rand()&255),600);
		}
		break;
	}
	le->leBounceSoundType = phase;
	// Draw a sprite
	CG_AddScaleFade(le);
}


void CG_AddFlashBang( localEntity_t *le ) {
	refEntity_t		
		*re = &le->refEntity;
	float			
		t = (float)( cg.time - le->startTime ) / ( le->endTime - le->startTime ),
		fScale	= 0.1f + ((t*t)*le->radius),
		c;
	

	VectorCopy(re->oldorigin, re->origin);

	c = (1.0f - (t));
	c = c*c;
	if (c>0.7)
		trap_R_AddLightToScene(re->origin, 700.0,	1,1,1);
	else
		trap_R_AddLightToScene(re->origin, 700.0*c, 1,1,1);

	c *= c * 0xff;
	re->shaderRGBA[0] = le->color[0] * c;
	re->shaderRGBA[1] = le->color[1] * c;
	re->shaderRGBA[2] = le->color[2] * c;
	re->shaderRGBA[3] = le->color[3] * c; //  * 0.75;

	// VectorCopy( dir, re->axis[0]);
	// MakeNormalVectors(re->axis[0],re->axis[1],re->axis[2]);
	// VectorScale(re->axis[0], fScaleU, re->axis[0]);
	// VectorScale(re->axis[1], fScale, re->axis[1]);
	// VectorScale(re->axis[2], fScale, re->axis[2]);
	
	re->axis[1][0] = fScale;
	re->axis[1][1] = 0;
	re->axis[1][2] = 0;

	re->axis[2][0] = 0;
	re->axis[2][1] = fScale;
	re->axis[2][2] = 0;

	re->axis[0][0] = 0;
	re->axis[0][1] = 0;
	re->axis[0][2] = fScale;
	
	re->nonNormalizedAxes = qtrue;

	re->origin[2] += 5;
	trap_R_AddRefEntityToScene( &le->refEntity );
}


// END bots - tbp

//==============================================================================

/*
===================
CG_AddLocalEntities

===================
*/
void CG_AddLocalEntities( void ) {
	localEntity_t	*le, *next;

	// walk the list backwards, so any new local entities generated
	// (trails, marks, etc) will be present this frame
	le = cg_activeLocalEntities.prev;
	for ( ; le != &cg_activeLocalEntities ; le = next ) {
		// grab next now, so if the local entity is freed we
		// still have it
		next = le->prev;

		if ( cg.time >= le->endTime ) {
			CG_FreeLocalEntity( le );
			continue;
		}
		switch ( le->leType ) {
		default:
			CG_Error( "Bad leType: %i", le->leType );
			break;

		case LE_MARK:
			break;

		case LE_SPRITE_EXPLOSION:
			CG_AddSpriteExplosion( le );
			break;

		case LE_EXPLOSION:
			CG_AddExplosion( le );
			break;

		case LE_FRAGMENT:			// gibs and brass
			CG_AddFragment( le );
			break;

		case LE_MOVE_SCALE_FADE:		// water bubbles
			CG_AddMoveScaleFade( le );
			break;

		case LE_FADE_RGB:				// teleporters, railtrails
			CG_AddFadeRGB( le );
			break;

		case LE_FALL_SCALE_FADE: // gib blood trails
			CG_AddFallScaleFade( le );
			break;

		case LE_SCALE_FADE:		// rocket trails
			CG_AddScaleFade( le );
			break;

// BEGIN bots - tbp
		case LE_NUKE_MUSHROOM:		// Yummy mushromm!
			CG_AddMushroom( le );
			break;
		case LE_NUKE_BALLOFFIRE:	// My great ball of fire!
			CG_AddBallOfFire( le );
			break;
		case LE_HEALRADIUS:
			CG_AddHealRadius( le ); // Nurse onboard!
			break;
		case LE_TENDRIL:
			CG_AddTendril( le );	// Tag, you're it!
			break;
		case LE_WARCRY:
			CG_AddWarcry( le );		// Rhaaaaaaaaaaaaaa!
			break;
		case LE_PARTICLE:
			CG_AddParticle( le );
			break;

		case LE_FLASHBANG:
			CG_AddFlashBang( le );
			break;
		case LE_EFFECT1:
			CG_AddEffect1( le );
			break;

		case LE_RAY:
			CG_AddRay( le );
			break;
// END bots - tbp
		}
	}
}




